package com.reger.datasource.core;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.Configuration;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;

import com.reger.datasource.properties.DruidProperties;
import com.reger.datasource.properties.MybatisNodeProperties;

import io.shardingjdbc.core.constant.DatabaseType;
import tk.mybatis.mapper.code.Style;
import tk.mybatis.spring.mapper.MapperScannerConfigurer;

public abstract class AbstractDataBaseBean implements DisposableBean {

	static Logger log = LoggerFactory.getLogger(AbstractDataBaseBean.class);

	protected final AbstractBeanDefinition createDataSource(MybatisNodeProperties nodeProperties, DruidProperties defaultProperties, String dataSourceName) {
		Assert.notNull(nodeProperties, String.format("DynamicDataSource 未初始化 ,dataSourceName=%s,失败原因: 配置参数为空,你的配置可能存在问题!", dataSourceName + ""));
		BeanDefinitionBuilder definitionBuilder = BeanDefinitionBuilder.rootBeanDefinition( DataSourceBulider.class,"bulid");
		definitionBuilder.addConstructorArgValue(nodeProperties);
		definitionBuilder.addConstructorArgValue(defaultProperties);
		return definitionBuilder.getRawBeanDefinition();
	}
    
	protected final AbstractBeanDefinition createTransactionManager(String dataSourceName) {
		BeanDefinitionBuilder bdb = BeanDefinitionBuilder.genericBeanDefinition(DataSourceTransactionManager.class);
		bdb.addConstructorArgReference(dataSourceName);
		return bdb.getRawBeanDefinition();
	}

	protected final AbstractBeanDefinition createJdbcTemplate(String dataSourceName) {
		BeanDefinitionBuilder bdb = BeanDefinitionBuilder.genericBeanDefinition(JdbcTemplate.class);
		bdb.addConstructorArgReference(dataSourceName);
		return bdb.getRawBeanDefinition();
	}

	protected Configuration cloneConfiguration(Configuration configuration) { 
		Configuration relust=new Configuration();
		if(configuration!=null) {
			relust.setEnvironment(configuration.getEnvironment());
			relust.setSafeRowBoundsEnabled(configuration.isSafeRowBoundsEnabled());
			relust.setSafeResultHandlerEnabled(configuration.isSafeResultHandlerEnabled());
			relust.setMapUnderscoreToCamelCase(configuration.isMapUnderscoreToCamelCase());
			relust.setAggressiveLazyLoading(configuration.isAggressiveLazyLoading());
			relust.setMultipleResultSetsEnabled(configuration.isMultipleResultSetsEnabled());
			relust.setUseGeneratedKeys(configuration.isUseGeneratedKeys());
			relust.setUseColumnLabel(configuration.isUseColumnLabel());
			relust.setCacheEnabled(configuration.isCacheEnabled());
			relust.setCallSettersOnNulls(configuration.isCallSettersOnNulls());
			relust.setUseActualParamName(configuration.isUseActualParamName());
			relust.setReturnInstanceForEmptyRow(configuration.isReturnInstanceForEmptyRow());
			relust.setLogPrefix(configuration.getLogPrefix());
			relust.setLogImpl(configuration.getLogImpl());
			relust.setVfsImpl(configuration.getVfsImpl());
			relust.setLocalCacheScope(configuration.getLocalCacheScope());
			relust.setJdbcTypeForNull(configuration.getJdbcTypeForNull());
			relust.setLazyLoadTriggerMethods(configuration.getLazyLoadTriggerMethods());
			relust.setDefaultStatementTimeout(configuration.getDefaultStatementTimeout());
			relust.setDefaultFetchSize(configuration.getDefaultFetchSize());
			relust.setDefaultExecutorType(configuration.getDefaultExecutorType());
			relust.setAutoMappingBehavior(configuration.getAutoMappingBehavior());
			relust.setAutoMappingUnknownColumnBehavior(configuration.getAutoMappingUnknownColumnBehavior());
			relust.setVariables(configuration.getVariables());
			relust.setReflectorFactory(configuration.getReflectorFactory());
			relust.setObjectFactory(configuration.getObjectFactory());
			relust.setObjectWrapperFactory(configuration.getObjectWrapperFactory());
			relust.setLazyLoadingEnabled(configuration.isLazyLoadingEnabled());
			relust.setProxyFactory(configuration.getProxyFactory());
			relust.setDatabaseId(configuration.getDatabaseId());
			relust.setConfigurationFactory(configuration.getConfigurationFactory());
		}
		relust.getTypeHandlerRegistry().setDefaultEnumTypeHandler(GlobalEnumTypeHandler.class);
		return relust;
	}

	public static final DatabaseType getDbType(DruidProperties nodeProperties, DruidProperties defaultProperties) {
//		String rawUrl = nodeProperties.getUrl();
//		if (StringUtils.isEmpty(nodeProperties.getUrl())) {
//			rawUrl = defaultProperties.getUrl();
//		}
//		String dbType = JdbcUtils.getDbType(rawUrl, null);
//		switch (dbType) {
//		case JdbcConstants.H2:
//			return DatabaseType.H2;
//		case JdbcConstants.MYSQL:
			return DatabaseType.MySQL;
//		case JdbcConstants.ORACLE:
//		case JdbcConstants.ALI_ORACLE:
//			return DatabaseType.Oracle;
//		case JdbcConstants.POSTGRESQL:
//			return DatabaseType.PostgreSQL;
//		case JdbcConstants.SQL_SERVER:
//			return DatabaseType.SQLServer;
//		}
//		return null;
	}
	
	protected final AbstractBeanDefinition createSqlSessionFactoryBean(String dataSourceName, String mapperPackage,
			String typeAliasesPackage, DatabaseType databaseType, Configuration configuration) {
		configuration.setDatabaseId(dataSourceName);
		BeanDefinitionBuilder bdb = BeanDefinitionBuilder.rootBeanDefinition(SqlSessionFactoryBean.class);
		bdb.addPropertyValue("configuration", configuration);
		bdb.addPropertyValue("failFast", true);
		bdb.addPropertyValue("typeAliases", this.saenTypeAliases(typeAliasesPackage));
		bdb.addPropertyReference("dataSource", dataSourceName);
		bdb.addPropertyValue("plugins", new Interceptor[] { new CustomPageInterceptor(databaseType) });
		if (!StringUtils.isEmpty(mapperPackage)) {
			try {
				mapperPackage = new StandardEnvironment().resolveRequiredPlaceholders(mapperPackage);
				String mapperPackages = ClassUtils.convertClassNameToResourcePath(mapperPackage);
				String mapperPackagePath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + mapperPackages + "/*.xml";
				Resource[] resources = new PathMatchingResourcePatternResolver().getResources(mapperPackagePath);
				bdb.addPropertyValue("mapperLocations", resources);
			} catch (Exception e) {
				log.error("初始化失败", e);
				throw new RuntimeException( String.format("SqlSessionFactory 初始化失败  mapperPackage=%s", mapperPackage + ""));
			}
		}
		return bdb.getBeanDefinition();
	}

	private static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";
	private static final String SEPERATOR = ",";

	private Class<?>[] saenTypeAliases(String typeAliasesPackage) {
		if (typeAliasesPackage == null || typeAliasesPackage.trim().isEmpty()) {
			return new Class<?>[] {};
		}
		ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
		MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resolver);

		String[] aliasList = typeAliasesPackage.split(SEPERATOR);
		List<Class<?>> result = new ArrayList<Class<?>>();
		for (String alias : aliasList) {
			if (alias == null || (alias = alias.trim()).isEmpty())
				continue;
			String aliasesPackages = ClassUtils.convertClassNameToResourcePath(alias);
			alias = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + aliasesPackages + "/" + DEFAULT_RESOURCE_PATTERN;
			try {
				Resource[] resources = resolver.getResources(alias);
				if (resources != null && resources.length > 0) {
					MetadataReader metadataReader = null;
					for (Resource resource : resources) {
						if (resource.isReadable()) {
							metadataReader = metadataReaderFactory.getMetadataReader(resource);
							try {
								result.add(Class.forName(metadataReader.getClassMetadata().getClassName()));
							} catch (ClassNotFoundException e) {
								e.printStackTrace();
							}
						}
					}
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return result.toArray(new Class<?>[0]);
	}

	protected final AbstractBeanDefinition createScannerConfigurerBean(String sqlSessionFactoryName, String basepackage,
			Mapper mappers, Order order,Style style, Properties properties) {
		BeanDefinitionBuilder bdb = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
		if(properties==null){
			properties = new Properties();
		}
		if(style!=null){
			properties.setProperty("style", style.name());
		}
		if(order!=null){
			properties.setProperty("ORDER", order.order);
		}
		if(mappers!=null){
			properties.setProperty("mappers",  mappers.mapper);
		}
		bdb.addPropertyValue("properties", properties);
		bdb.addPropertyValue("sqlSessionFactoryBeanName", sqlSessionFactoryName);
		bdb.addPropertyValue("basePackage", basepackage);
		return bdb.getRawBeanDefinition();
	}

	@Override
	public void destroy() throws Exception {
		DataSourceBulider.stop();
	}
}
